關於變數的使用,這裡讓我們再探討得深入一些。
當程式執行時,對變數的操作分為以下兩種:
從英文全名可以看到,它分別指左邊和右邊,誰的左邊和右邊?是等號,也就是 =
賦值運算子(assignment operator)的左右邊。
如果用數學運算式 x + y = 5
來舉例:
LHS → x + y
RHS → 5
套回到程式邏輯中:
=
的左邊 → 變數賦值
=
左邊的變數,不關心變數的值=
的右邊 → 變數取值
=
右邊取出變數的值,無賦值行為雖然 LHS
和 RHS
的名稱來源於賦值運算子,但這兩者發生時,不一定需要賦值運算子存在,以下進行更詳細的說明:
=
)的左側=
左側的變數並「賦值」的行為=
發生,也可能通過向函式傳遞(賦予)參數而發生範例
var name = "Carol";
// 將字串 "Carol" 賦值給 name,發生了 LHS 查詢
function foo(a) { ... }
// 調用函數時,傳入的參數被賦值給 a,發生了 LHS 查詢
=
)的右側=
右側的變數並「取值」的行為範例
var totalPrice = amount * 2;
// 取得 amount 的值以進行計算,發生了 RHS 查詢
console.log(totalPrice)
// 取得 console 的值,發生了 RHS 查詢
// 程式調用 console 物件內的 log 屬性,發現是一個函式
// 取得 totalPrice 的值來執行 log 函式,發生了 RHS 查詢
在實作當中,LHS 和 RHS 經常交錯出現,以下舉一些例子:
範例一
var name = "Charles"; // LHS
var king = name; // RHS + LHS
var name = "Charles";
賦值一個字串給變數 name
var king = name;
先進行 RHS 取得 name
的值king
範例二
function foo(a) {
// 進入函式
// 2. LHS,將傳入的參數 "Hello" 賦值給 a
console.log(a);
// 3. RHS,取得全域物件屬性 console,並執行屬性查詢取得 log
// 4. RHS,取得 a 的值並進行函式調用,列印出"Hello"
}
foo("Hello");
// 1. RHS,回傳 foo 的值,發現是函數並進行調用
foo(2)
時,程式發出 RHS 查詢,得知 foo
是一個函式,於是傳入參數準備執行。foo
內部後,參數 2
被賦值給 a
,發生 LHS 查詢foo
內部,遇到 console
時發出 RHS 查詢,找到是全域物件的屬性,並發現 console.log
是一個函式。console.log( a );
時發生 RHS 查詢,找到 a
的值並回傳執行。
function foo(){...}
的部分 ,已在編譯時期處理完了函式宣告和函式賦值,因此當引擎執行程式時,不會進行 LHS 查詢。
由於變數是以「參考(Reference)」方式儲存在記憶體中,當變數查詢失敗時,不論是 LHS 或 RHS,程式都會拋出 ReferenceError
,意即無法取得記憶體中對應的來源參考。
參考可視為變數在記憶體中的住家地址,程式藉著識別值找到某變數,就是依照參考去對應變數在記憶體中所處的位置。
如果兩個變數的參考相同,代表他們指向記憶體相同的位置。當程式改變其中一個變數的內容,等同於改變擁有相同地址的另一個變數的內容。
以下分別細說 LHS 和 RHS 錯誤發生時的狀況:
在 JS 的「預設模式(Default Mode)」下執行 LHS 查詢卻找不到該變數時,全域作用域會直接創造一個新變數,並回傳這個變數。
這種情況發生有兩種可能,一是試圖賦值給從未宣告的變數,二是該變數不在當前可合法存取的作用域範圍內。
ES5 以後新增的「嚴謹模式(Strict Mode)」則不允許自動/隱含地創建全域變數,LHS 查詢失敗時,引擎依舊會拋出 ReferenceError
錯誤。
RHS 在查詢成功取得變數的值後,如果對該值進行不合法的行為(指這個值不可能做到的事情,比方說將非函式作為函式調用),則程式會拋出
TypeError
。